﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics.Contracts;

namespace System.Linq.Expressions
{
	/// <summary>
	/// Obtém o valor dos parâmetros externos
	/// </summary>
	internal sealed class ExternalParametersResolver : ExpressionVisitor
	{

		public ExternalParametersResolver( IEnumerable<KeyValuePair<string, ParameterExpression>> exchanges )
			: base()
		{
			this.Exchanges = new Dictionary<string, ParameterExpression>();
			if ( exchanges != null )
				exchanges.Each( item => this.Exchanges.Add( item.Key, item.Value ) );
		}

		/// <summary>
		/// 
		/// </summary>
		public Dictionary<string, ParameterExpression> Exchanges
		{
			get;
			private set;
		}

		/// <summary>
		/// Se a propriedade retorna um struct, então passado o valor ao invés da propriedade
		/// </summary>
		/// <param name="node"></param>
		/// <returns></returns>
		protected override Expression VisitMember( MemberExpression node )
		{
			if ( node.Type.IsValueType )
			{
				Func<object> valueGetter = Expression.Lambda<Func<object>>( Expression.Convert( node, typeof( object ) ) ).Compile();
				return Expression.Constant( valueGetter(), node.Type );
			}
			else
				return base.VisitMember( node );
		}

		/// <summary>
		/// Altera o parâmetro de uma expressão lamba por uma variável
		/// </summary>
		/// <param name="node"></param>
		/// <returns></returns>
		protected override Expression VisitParameter( ParameterExpression node )
		{
			ParameterExpression exchangeFor;

			if ( this.Exchanges.TryGetValue( node.Name, out exchangeFor ) && node.Type == exchangeFor.Type )
				return exchangeFor;
			else
				return base.VisitParameter( node );
		}
	}


	public static class ExternalParametersResolverExtensions
	{
		public static Expression ResolveExternals( this Expression exp )
		{
			return exp.ResolveExternals( null );
		}

		public static Expression ResolveExternals( this Expression exp, IDictionary<String, ParameterExpression> variablesExchange )
		{
			if ( exp == null )
				return exp;

			ExternalParametersResolver resolver = new ExternalParametersResolver( variablesExchange );
			return resolver.Visit( exp );
		}
	}
}
